﻿using System;
using System.Collections.Generic;
using System.Threading;
using Lidgren.Network;
using ObjectSync.Interfaces;
using ObjectSync.Util;
using System.Text;

namespace ObjectSync
{
    public class ClientCommunicator: ICommunicator
    {
		#region ICommunicator implementation

		public void Send (NetBuffer sendBuffer)
		{
			NetOutgoingMessage msg = Client.CreateMessage();
			msg.Write(sendBuffer.ReadBytes(sendBuffer.LengthBytes));

			Send(msg);
		}

		public NetOutgoingMessage GetOutMenssage ()
		{
			return Client.CreateMessage();
		}

		#endregion

		private NetPeerConfiguration Config;

        private NetClient Client;

        private int port = 1717;
        private string server = "127.0.0.1";
        private object locker = new object();
        private bool keepReaderRunning = true;
        /// <summary>
        /// WARNING only change this variable with the locker locked. It is accessed in more then one thread
        /// </summary>
        private List<Message> messages = new List<Message>();
        private Thread reader;

		private NetIncomingMessage Msg;
		private NetDeliveryMethod defaultNetChannel;

		private comStatus status = comStatus.diconected;

		public comStatus Status {
			get {
				return status;
			}
		}

		public comStatus getStatus () {return Status;}


		public NetDeliveryMethod DefaultDeliverMethood
        {
            get { return defaultNetChannel; }
            set { defaultNetChannel = value; }
        }


        public ClientCommunicator(string server, int port)
        {
            this.server = server;
            this.port = port;
            Initiate();
        }
        public void Initiate()
        {
            // create a client with a default configuration
            //Config = new NetConfiguration("ObjectSync");

			Config = new NetPeerConfiguration("ObjectSync");
			Config.Port = port;
			Config.AcceptIncomingConnections = true;

			Config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse);

			DefaultDeliverMethood = NetDeliveryMethod.ReliableOrdered;

            Client = new NetClient(Config);
           
			Client.Start();

			//Client.DiscoverLocalPeers(port);
			Client.DiscoverKnownPeer(server, port);

            // Wait half a second to allow server to start up if run via Visual Studio
            Thread.Sleep(800);


			//NetConnection con = Client.Connect(server,port);

            // create a buffer to read data into
            //Buffer = Client.CreateBuffer();
			//Buffer = new NetBuffer();

            // create the thread reader
            ThreadStart job = new ThreadStart(Reader);
            reader = new Thread(job);
            reader.Start();

			//Reader();
        }

        public void Write(string message)
        {
            Write(message, DefaultDeliverMethood);
        }

        public void Write(byte[] message)
        {
            Write(message, DefaultDeliverMethood);
        }

        public void Send(NetOutgoingMessage sendBuffer)
        {
            Send(sendBuffer, DefaultDeliverMethood);
        }

		public void Write(string message, NetDeliveryMethod channel)
        {
            var enc = new UnicodeEncoding();

            byte[] mbytes = enc.GetBytes(message);

            Write(mbytes, channel);
        }

		public void Write(byte[] message, NetDeliveryMethod channel)
        {
			NetOutgoingMessage sendBuffer = Client.CreateMessage();
            sendBuffer.Write(message);
            Send(sendBuffer, channel);
        }

		public void Send(NetOutgoingMessage sendBuffer, NetDeliveryMethod channel)
        {
            int z;
            // This makes it try to send the message (10) times befor giving up 
            for (z = 0; z < ConfigManager.Instance.CommunicationRetries; z++)
            {
                try
                {

                    Client.SendMessage(sendBuffer, channel);
                    z = ConfigManager.Instance.CommunicationRetries + 1;
                }
                catch
                {
                    Thread.Sleep(500);
                }
            }
            if (z == ConfigManager.Instance.CommunicationRetries) throw new Exception("Erro tring to contact the server");

        }

        public Message[] Read()
        {
            lock (locker)
            {
                Message[] answer = messages.ToArray();
                messages.Clear();
                return answer;
            }
        }

        private void Reader()
        {
            while (keepReaderRunning)
            {

                // check if any messages has been received
                while ((Msg = Client.ReadMessage()) != null)
                {
                    switch (Msg.MessageType)
                    {

						case NetIncomingMessageType.DiscoveryResponse:
							Console.WriteLine("Found server: " + Msg.SenderEndPoint.ToString() + " Hail: " + Msg.ReadString() );
							Client.Connect(Msg.SenderEndPoint);
							
							//Client.Connect(Msg.SenderEndPoint);
                           break;
						case NetIncomingMessageType.VerboseDebugMessage:
                            Console.WriteLine(Msg.ReadString());
                            break;
						case NetIncomingMessageType.StatusChanged:
                            //Console.WriteLine(String.Format("New status: {0} ({1})", Client.Status, Msg.ReadString()));
							if(Client.ConnectionStatus == NetConnectionStatus.Connected ) status = comStatus.connected;
							else status = comStatus.diconected;
                            break;
						case NetIncomingMessageType.Data:
                            // The server sent this data!
                            //string msg = Buffer.ReadString();
                            UInt16 msgType = Msg.ReadUInt16();

                            //Let's see if it's a SYNC
                            if (msgType == (UInt16)MessageType.SYNC)
                            {
                                Syncronization.DecodeMessageAndSync(Msg);
                            }
                            else if (msgType == (UInt16)MessageType.ExecResp)
                            {
                                //TODO verify thread safety
                                RemoteManager.RegisterAnswer(Msg);
                            }
                            else
                            {
                            
                                Message m = new Message();
                                m.type = Msg.MessageType;
                                m.msg = Msg.ReadBytes(Msg.LengthBytes);

                                lock (locker) messages.Add(m);
                            }
                    
                            break;
                    }
                }

                Thread.Sleep(1);
            }
        }

    }
}
